
#include <genericdeletedialog.h>
#include <genericrenamedialog.h>
#include <genericsavedialog.h>
#include <materialmanager.h>
#include <material_editor/materialview.h>
#include <material_editor/shadereditor.h>
#include <material_editor/textureeditor.h>
#include <session.h>
#include <QGridLayout>
#include <QMenu>
#include <QMenuBar>


MaterialView::MaterialView (MaterialManager* manager, QWidget* parent)
  : BaseView (parent)
  , _manager (manager)
  , _selectNewItems(false)
{
  for (unsigned i=0; i<Action_COUNT; ++i)
    {
      _actions[i] = 0;
    }
  InitGUI ();
  InitActions ();
  InitModel ();
}

void MaterialView::InitGUI ()
{
  QGridLayout* layout = new QGridLayout (this);

  QSplitter* splitter = new QSplitter (this);
  layout->addWidget (splitter, 0, 0, 1, 1);

  _overview = new QTreeView (this);
  _editorPanels = new QTabWidget (this);

  _editorPanels->setDocumentMode(true);
  _editorPanels->setMovable(true);
  _editorPanels->setTabsClosable(true);

  _overview->setFrameShadow(QFrame::Sunken);
  _overview->setFrameShape(QFrame::Box);
  _overview->setContextMenuPolicy (Qt::CustomContextMenu);
  _overview->setHeaderHidden(true);
  _overview->setAnimated(true);
  _overview->setSortingEnabled(true);
  _overview->setSelectionMode(QAbstractItemView::SingleSelection);

  splitter->addWidget(_overview);
  splitter->addWidget(_editorPanels);

  splitter->setOpaqueResize(false);
  splitter->setStretchFactor(0, 0);
  splitter->setStretchFactor(1, 1);
  splitter->setSizes (QList<int> () << width () * 20 / 100 << width () * 80 / 100);

  connect (_overview, SIGNAL(customContextMenuRequested (const QPoint&)), 
           this, SLOT(ContextMenuRequested(const QPoint&)));
  connect (_overview, SIGNAL(doubleClicked(const QModelIndex&)), 
           this, SLOT(ItemDoubleClicked(const QModelIndex&)));
  connect (_editorPanels, SIGNAL(tabCloseRequested(int)),
           this, SLOT(TabCloseRequested(int)));
}

void MaterialView::InitActions ()
{
  _actions[Action_TextureAdd] = new QAction (QIcon(":/icons/media/texture_add.png"), "Add texture", this);
  _actions[Action_TextureEdit] = new QAction (QIcon(":/icons/media/texture_edit.png"), "Edit texture", this);

  _actions[Action_ShaderAdd] = new QAction ("Add shader", this);
  _actions[Action_ShaderEdit] = new QAction ("Edit shader", this);
  _actions[Action_ShaderChangeType] = new QAction("Change type", this);

  _actions[Action_ProgramAdd] = new QAction ("Add program", this);
  _actions[Action_ProgramEdit] = new QAction ("Edit program", this);

  _actions[Action_EffectAdd] = new QAction ("Add effect", this);
  _actions[Action_EffectEdit] = new QAction ("Edit effect", this);

  _actions[Action_MaterialAdd] = new QAction ("Add material", this);
  _actions[Action_MaterialEdit] = new QAction ("Edit material", this);

  _actions[Action_MaterialDataRename] = new QAction (QIcon(":/icons/media/texture_rename.png"), "Rename...", this);
  _actions[Action_MaterialDataDelete] = new QAction (QIcon(":/icons/media/texture_delete.png"), "Remove", this);
  _actions[Action_MaterialDataGroupRename] = new QAction (QIcon(":/icons/media/texture_group_rename.png"), "Rename group...", this);
  _actions[Action_MaterialDataGroupDelete] = new QAction (QIcon(":/icons/media/texture_group_delete.png"), "Delete group", this);

  connect (_actions[Action_TextureEdit], SIGNAL(triggered ()), this, SLOT(TextureEditCurrent ()));
  connect (_actions[Action_TextureAdd], SIGNAL(triggered()), this, SLOT(TextureAdd ()));

  connect (_actions[Action_ShaderEdit], SIGNAL(triggered ()), this, SLOT(ShaderEditCurrent ()));
  connect (_actions[Action_ShaderAdd], SIGNAL(triggered()), this, SLOT(ShaderAdd ()));
  connect (_actions[Action_ShaderChangeType], SIGNAL(triggered()), this, SLOT(ShaderChangeTypeCurrent ()));

  connect (_actions[Action_ProgramEdit], SIGNAL(triggered ()), this, SLOT(ProgramEditCurrent ()));
  connect (_actions[Action_ProgramAdd], SIGNAL(triggered()), this, SLOT(ProgramAdd ()));

  connect (_actions[Action_EffectEdit], SIGNAL(triggered ()), this, SLOT(EffectEditCurrent ()));
  connect (_actions[Action_EffectAdd], SIGNAL(triggered()), this, SLOT(EffectAdd ()));

  connect (_actions[Action_MaterialEdit], SIGNAL(triggered ()), this, SLOT(MaterialEditCurrent ()));
  connect (_actions[Action_MaterialAdd], SIGNAL(triggered()), this, SLOT(MaterialAdd ()));

  connect (_actions[Action_MaterialDataRename], SIGNAL(triggered()), this, SLOT(MaterialDataRenameCurrent()));
  connect (_actions[Action_MaterialDataDelete], SIGNAL(triggered ()), this, SLOT(MaterialDataRemoveCurrent ()));
  connect (_actions[Action_MaterialDataGroupRename], SIGNAL(triggered()), this, SLOT(MaterialDataGroupRename()));
  connect (_actions[Action_MaterialDataGroupDelete], SIGNAL(triggered()), this, SLOT(MaterialDataGroupRemove ()));
}


void MaterialView::InitModel ()
{
  _overviewModel = new BaseItemModel ();

  TextureCategoryItem* ti = new TextureCategoryItem (this, Category("Textures"), false);
  _items[ti->GetCategory().GetBaseName()] = ti;
  _overviewModel->GetRoot()->Add (ti);

  ShaderCategoryItem* si = new ShaderCategoryItem (this, Category("Shaders"), false);
  _items[si->GetCategory().GetBaseName()] = si;
  _overviewModel->GetRoot()->Add (si);

  ProgramCategoryItem* pi = new ProgramCategoryItem (this, Category("Programs"), false);
  _items[pi->GetCategory().GetBaseName()] = pi;
  _overviewModel->GetRoot()->Add (pi);

  EffectCategoryItem* ei = new EffectCategoryItem (this, Category("Effects"), false);
  _items[ei->GetCategory().GetBaseName()] = ei;
  _overviewModel->GetRoot()->Add (ei);

  MaterialCategoryItem* mi = new MaterialCategoryItem (this, Category("Materials"), false);
  _items[mi->GetCategory().GetBaseName()] = mi;
  _overviewModel->GetRoot()->Add (mi);

  _overview->setModel (_overviewModel);

  connect (_manager,  SIGNAL(MaterialDataChanged(MaterialData*)),
           this,      SLOT(MaterialDataChanged(MaterialData*)));
  connect (_manager,  SIGNAL(MaterialDataRemoved(MaterialData*)),
           this,      SLOT(MaterialDataRemoved(MaterialData*)));
  connect (_manager, SIGNAL(TextureAdded(Texture*)), this, SLOT(TextureAdded(Texture*)));
  connect (_manager, SIGNAL(ShaderAdded(Shader*)), this, SLOT(ShaderAdded(Shader*)));
  connect (_manager, SIGNAL(ProgramAdded(Program*)), this, SLOT(ProgramAdded(Program*)));
  connect (_manager, SIGNAL(EffectAdded(Effect*)), this, SLOT(EffectAdded(Effect*)));
  connect (_manager, SIGNAL(MaterialAdded(Material*)), this, SLOT(MaterialAdded(Material*)));

  connect (_overview->selectionModel(), SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)),
           this, SLOT(SelectionChanged (const QModelIndex&, const QModelIndex&)));


}

void MaterialView::ConnectSignals()
{
  MainWindow* mw = Session::Get()->GetMainWindow();

  connect (mw->GetAction(MainWindow::Action_Save), SIGNAL(triggered()), this, SLOT(Save ()));
  connect (mw->GetAction(MainWindow::Action_SaveAll), SIGNAL(triggered()), this, SLOT(SaveAll ()));
  connect (mw->GetAction(MainWindow::Action_Compile), SIGNAL(triggered()), this, SLOT(Compile ()));
}

void MaterialView::CreateMenu(MainWindow *mainWindow)
{
  QMenuBar* mb = mainWindow->menuBar();

  QMenu *menu = mb->addMenu("Materials");
  menu->addAction(_actions[Action_MaterialDataRename]);
  menu->addAction(_actions[Action_MaterialDataDelete]);
  menu->addAction(_actions[Action_MaterialDataGroupRename]);
  menu->addAction(_actions[Action_MaterialDataGroupDelete]);
  menu->addSeparator();
  menu->addAction(_actions[Action_TextureAdd]);
  menu->addAction(_actions[Action_TextureEdit]);
  menu->addSeparator();
  menu->addAction(_actions[Action_ShaderAdd]);
  menu->addAction(_actions[Action_ShaderEdit]);
  menu->addAction(_actions[Action_ShaderChangeType]);
  menu->addSeparator();
  menu->addAction(_actions[Action_ProgramAdd]);
  menu->addAction(_actions[Action_ProgramEdit]);
  menu->addSeparator();
  menu->addAction(_actions[Action_EffectAdd]);
  menu->addAction(_actions[Action_EffectEdit]);
  menu->addSeparator();
  menu->addAction(_actions[Action_MaterialAdd]);
  menu->addAction(_actions[Action_MaterialEdit]);
}

void MaterialView::EnableDisableActions(BaseItem *item)
{

  for (unsigned i=0; i<Action_COUNT; ++i)
    {
      if (_actions[i])
        {
          _actions[i]->setEnabled(false);
        }
    }

  if (!item)
    {
      return;
    }

  item->EnableActions();
}

void MaterialView::SelectionChanged(const QModelIndex &newIndex, const QModelIndex &oldIndex)
{
  BaseItem* item = _overviewModel->GetItem(newIndex);
  EnableDisableActions(item);
}

void MaterialView::ContextMenuRequested (const QPoint& point)
{
  BaseItem* item = _overviewModel->GetItem(_overview->indexAt(point));
  EnableDisableActions(item);
  if (!item)
    {
      return;
    }

  QMenu* menu = new QMenu ();
  if (!item->BuildMenu (menu))
    {
      menu->deleteLater();
      return;
    }

  menu->popup (_overview->mapToGlobal (point));
}

void MaterialView::ItemDoubleClicked (const QModelIndex& index)
{
  BaseItem* item = _overviewModel->GetItem(index);
  if (!item)
    {
      return;
    }
  item->Trigger ();
}

void MaterialView::EditorDirtyChanged (bool dirty)
{
  UpdateEditorLabel (static_cast<BaseEditorWidget*>(sender ()));
}

void MaterialView::EditorNameChanged (const QString& name)
{
  UpdateEditorLabel (static_cast<BaseEditorWidget*>(sender ()));
}

void MaterialView::UpdateEditorLabel (BaseEditorWidget* editor)
{
  int index = _editorPanels->indexOf(editor);
  if (index == -1)
    {
      return;
    }

  QString name = editor->GetName ();
  if (editor->IsDirty ())
    {
      name += "*";
    }
  _editorPanels->setTabText (index, name);
}

void MaterialView::TabCloseRequested(int index)
{
  BaseEditorWidget* editor = static_cast<BaseEditorWidget*>(_editorPanels->widget(index));
  if (!editor)
    {
      _editorPanels->removeTab(index);
      return;
    }

  if (editor->IsDirty())
    {
      GenericSaveDialog dlg (Session::Get()->GetMainWindow());
      dlg.SetSaveType(editor->GetTypeName());
      dlg.SetSaveMessage(editor->GetUnsaveMessage());
      dlg.exec();

      switch (dlg.GetResult())
        {
          case GenericSaveDialog::Result_Cancel:
            return;
          case GenericSaveDialog::Result_Save:
            editor->Save();
            break;
          case GenericSaveDialog::Result_DontSave:
            break;
        }
    }
  RemoveEditorWithData(editor->GetData());
}

void MaterialView::SelectItem (BaseItem* item)
{
  if (!_selectNewItems)
    {
      return;
    }

  DirectSelectItem(item);
}

void MaterialView::DirectSelectItem (BaseItem* item)
{
  QModelIndex index = _overviewModel->CreateModelIndex(item);
  _overview->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
  _overview->scrollTo(index);
}

void MaterialView::RemoveEditorWithData(void *data)
{
  if(_editors.contains(data))
    {
      BaseEditorWidget* editor = _editors[data];
      if (!editor)
        {
          return;
        }
      int index = _editorPanels->indexOf(editor);
      if (index != -1)
        {
          _editorPanels->removeTab(index);
        }
      _editors.remove(data);
    }
}


BaseItem* MaterialView::GetItem (MaterialData* materialItem)
{
  if (!materialItem)
    {
      return 0;
    }

  const Category& cat = materialItem->GetCategory();
  if (!_items.contains(cat.GetBaseName()))
    {
      return 0;
    }

  return _items[cat.GetBaseName()]->Find(materialItem);
}

CategoryItem* MaterialView::GetCategoryItem(MaterialData* materialItem)
{
  if (!materialItem)
    {
      return 0;
    }

  const Category& cat = materialItem->GetCategory();
  if (!_items.contains(cat.GetBaseName()))
    {
      return 0;
    }

  CategoryItem* item = _items[cat.GetBaseName()];

  for (int i=1; i<cat.GetNumberOfStages(); i++)
    {
      QString name = cat.GetStageName(i);
      if (name.length() == 0)
        {
          continue;
        }
      item = item->Get(cat.GetStageName(i));
    }
  return item;
}

void MaterialView::AddItem(BaseItem *item, MaterialData *materialItem)
{
  CategoryItem* parent = GetCategoryItem(materialItem);
  parent->Add(item);
  SelectItem(item);
}


void MaterialView::MaterialDataChanged(MaterialData *data)
{
  BaseItem* bi = GetItem(data);
  if (!bi)
    {
      return;
    }

  //
  // Find the current parent category and test if the category has changed
  CategoryItem* ci = static_cast<CategoryItem*>(bi->GetParent());
  if (!ci)
    {
      return;
    }

  Category catCi = ci->GetCategory();
  Category catData = data->GetCategory();
  if (catCi != catData)
    {
      // The category has changed so we must move the item
      ci->Remove(bi);
      CategoryItem* newCi = GetCategoryItem(data);
      newCi->Add(bi);
    }

}

void MaterialView::MaterialDataRemoved(MaterialData *data)
{
  BaseItem* bi = GetItem(data);
  if (!bi)
    {
      return;
    }

  bi->RemoveSelf();
}

BaseEditorWidget* MaterialView::OpenEditor(void *materialData)
{
  if (_editors.contains(materialData))
    {

      BaseEditorWidget* editor = _editors[materialData];
      if (editor)
        {
          _editorPanels->setCurrentWidget (editor);
          return editor;
        }
    }

  return 0;
}


void MaterialView::InsertEditor (BaseMaterialEditorWidget* editor)
{
  if (!editor)
    {
      return;
    }
  connect (editor, SIGNAL(DirtyChanged(bool)), this, SLOT(EditorDirtyChanged(bool)));
  connect (editor, SIGNAL(NameChanged(const QString&)), this, SLOT(EditorNameChanged(const QString&)));

  _editors[editor->GetData()] = editor;
  int index = _editorPanels->addTab (editor, editor->GetName ());
  editor->layout()->setContentsMargins(0, 4, 0, 0);
  _editorPanels->setCurrentIndex (index);
}


void MaterialView::MaterialDataRenameCurrent()
{

  MaterialDataItem* item = get_current_item<MaterialDataItem>(_overview);
  if (!item)
    {
      return;
    }
  MaterialData* data = item->GetMaterialData();

  GenericRenameDialog dlg (Session::Get()->GetMainWindow());
  dlg.SetType("Rename", data->GetTypeName());
  dlg.SetName(data->GetName());
  dlg.SetCategory(data->GetCategory());
  if (dlg.exec() == QDialog::Rejected)
    {
      return;
    }

  Category cat = dlg.GetCategory();
  data->SetName(dlg.GetName());
  data->SetCategory(dlg.GetCategory());
  data->Save();
}

void MaterialView::MaterialDataRemoveCurrent()
{
  MaterialDataItem* item = get_current_item<MaterialDataItem>(_overview);
  if (!item)
    {
      return;
    }

  MaterialData* data = item->GetMaterialData();
  if (GenericDeleteDialog::Delete(Session::Get()->GetMainWindow(),
                                  data->GetTypeName(),
                                  QString("Are you sure to delete the %1?").arg(data->GetTypeName())))
    {
      data->Remove();
    }
}

void MaterialView::MaterialDataGroupRename()
{
  CategoryItem* item = get_current_item<CategoryItem>(_overview);
  QString name = GenericRenameDialog::GetName(Session::Get()->GetMainWindow(),
                                              "Rename",
                                              "Group",
                                              item->GetName(),
                                              true);
  if (name == QString::Null())
    {
      return;
    }

  Category from = item->GetCategory();
  Category to = item->GetCategory();
  to.RenameHead(name);

  _manager->RenameMaterialDataGroup(from, to);

  item->Cleanup();
}



void MaterialView::MaterialDataGroupRemove()
{
  CategoryItem* item = get_current_item<CategoryItem>(_overview);
  if (!item)
    {
      return;
    }

  if (GenericDeleteDialog::Delete(Session::Get()->GetMainWindow(),
                                  "Group",
                                  "Are you sure to delete the group?"))
    {
      Category name = item->GetCategory();


      _manager->RemoveMaterialDataGroup(name);
      BaseItem* parent = item->GetParent();
      item->RemoveSelf();
      DirectSelectItem(parent);
    }
}

void MaterialView::Save()
{
  BaseEditorWidget* editor = static_cast<BaseEditorWidget*>(_editorPanels->currentWidget());
  if (editor)
    {
      editor->Save();
    }

  _manager->Save(MaterialManager::DV_All);
}

void MaterialView::SaveAll()
{
  for (int i=0, j=_editorPanels->count(); i<j; i++)
    {
      BaseEditorWidget* editor = static_cast<BaseEditorWidget*>(_editorPanels->widget(i));
      if (editor)
        {
          editor->Save();
        }
    }
  _manager->Save(MaterialManager::DV_All);

}

void MaterialView::Compile()
{
  BaseEditorWidget* editor = static_cast<BaseEditorWidget*>(_editorPanels->currentWidget());
  if (editor)
    {
      editor->Compile();
    }
}
